#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <ctype.h>
#include <pthread.h>
#include <signal.h>
#include <getopt.h>
#include <sys/time.h>
#include <sys/ioctl.h>

#ifdef WIN32
#include <windows.h>
#define sleep(n)  Sleep(n * 1000)
#define usleep(n) Sleep(n / 1000)
#else
#include <sys/stat.h>
#include <unistd.h>
#endif

#include "bb_api.h"
#include "ar_net_api.h"

#define RANDOM(x) (rand()%x)

static int net_dev_fd = -1;
static bb_host_t* phost=NULL;
static bb_dev_handle_t *bb_handle;
static bool net_create_event = false;

static bool b_ip = false;
static bool b_mtu = false;
static char ip_str[256] = {0};
static int mtu = 1500;

static void cmd_help(char * name)
{
    printf("Usage: %s [options]\n", name);
    printf("Options:\n");
    printf("\t-h       Print help info \n");
    printf("\t-c       Create the port\n");
    printf("\t-d       Destroy the port\n");
    printf("\t-i       Device id\n");
    printf("\t-v       Get net version\n");
    printf("\t-V       Get driver version\n");
    printf("\t-n       Net dev name\n");
    printf("\t-m       Mac address(such as AA:BB:CC:DD:EE:FF)\n");
    printf("\t-s       Slot id\n");
    printf("\t-p       Port id\n");
    printf("\t-t       Tx buffer size\n");
    printf("\t-r       Rx buffer size\n");
    printf("\t-T       type(0:port 1:vlan)\n");
    printf("\t-I       Operate as ioctl mode\n");
    printf("\t-P       Ip address\n");
    printf("\t-M       MTU\n");
    printf("\t-D       Run as daemon\n");
}

typedef	void *(*ThreadEntryPtrType)(void *);

static int MID_UTIL_CreateDetachThread(ThreadEntryPtrType entry, void *para, pthread_t *pid)
{
    pthread_t ThreadId;
    pthread_attr_t attr;

    pthread_attr_init(&attr);
    pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if(pthread_create(&ThreadId, &attr, entry, para) == 0) {
        pthread_attr_destroy(&attr);
		if(pid)
			*pid = ThreadId;
		return 0;
    }
    pthread_attr_destroy(&attr);
    return -1;
}

static void cmd_version_get(int fd)
{
    int ret = 0;
    int version;

    ret = ioctl(fd, AR_CHARDEV_IOCTL_NET_GET_VERSION, &version);
    if (ret < 0)
        printf("ioctl: %s\n", strerror(errno));
    else
        printf("version:0x%x\n", version);
}

static void cmd_version_drv_get(int fd)
{
    int ret = 0;
    ar_chrdev_get_version_t ar_chrdev_get_version = {0};

    ret = ioctl(fd, AR_CHARDEV_IOCTL_GET_VERSION, &ar_chrdev_get_version);
    if (ret < 0)
        printf("ioctl: %s\n", strerror(errno));
    else
        printf("version:%s\n", ar_chrdev_get_version.version);
}

static int cmd_port_handle(int fd, ar_netif_t *ar_netif)
{
    int ret = 0;

    ret = ioctl(fd, AR_CHARDEV_IOCTL_NET_HANDLE_NETIF, ar_netif);
    if (ret < 0)
        printf("ioctl: %s\n", strerror(errno));
    else
        printf("Send cmd port_handle success.\n");

    return ret;
}

static struct option main_options[] = {
    {"help",         0, 0, 'h'},
    {"port_create",  0, 0, 'c'},
    {"port_destroy", 0, 0, 'd'},
    {"id",           0, 0, 'i'},
    {"version",      0, 0, 'v'},
    {"drv_version",  0, 0, 'V'},
    {"name",         0, 0, 'n'},
    {"mac",          0, 0, 'm'},
    {"slot",         0, 0, 's'},
    {"port",         0, 0, 'p'},
    {"tx_buf",       0, 0, 't'},
    {"rx_buf",       0, 0, 'r'},
    {"type",         0, 0, 'T'},
    {"ioctl",        0, 0, 'I'},
    {"ip",           0, 0, 'P'},
    {"MTU",          0, 0, 'M'},
    {"daemon_mode",  0, 0, 'D'},
    {"resize",       0, 0, 'R'},
    {0, 0, 0, 0}
};

static void bb_event_hotplug(void* arg, void* user)
{
    int ret = 0;
    int try_count = 10;
    bb_event_hotplug_t* plug = (bb_event_hotplug_t*)arg;

    printf("dev hotplug cb id = %d mac = %s status = %s\n", plug->id, plug->bb_mac.mac, plug->status ? "on" : "off");

    if (!plug->status) {
        bb_handle = NULL;
        net_dev_fd = -1;
    }
    else {
        int dev_num;
        bb_dev_t** devs=NULL;

        while (try_count > 0) {
            try_count--;

            /* get dev list of the 8030 */
            dev_num = bb_dev_getlist(phost,&devs);
            if (dev_num == 0) {
                printf("Get no device!\n");
                sleep(1);
                continue;
            }
            printf("dev num %d\n", dev_num);

            /* Open the dev 8030 */
            bb_handle = bb_dev_open(devs[0]);
            if (!bb_handle) {
                printf("bb_dev_open error\n");
                bb_dev_freelist(devs);
                sleep(1);
                continue;
            }

            printf("bb_dev_open success\n");

            bb_dev_freelist(devs);

            net_dev_fd = bb_net_dev_open(bb_handle);
            if (net_dev_fd < 0) {
                printf("bb_net_dev_open error\n");
                bb_dev_close(bb_handle);
                sleep(1);
                continue;
            }

            printf("net dev fd open success\n");

            break;
        }
    }

    if (bb_handle && (net_dev_fd >= 0)) {
        net_create_event = true;
    }
}

void * net_create_event_process(void * args)
{
    int ret = 0;
    char sys_cmd[256] = {0};
    ar_netif_t *ar_netif = (ar_netif_t *)args;

    if (!ar_netif) {
        printf("ar_netif is NULL!\n");
        return NULL;
    }

    while (1) {
        if (net_create_event && bb_handle && (net_dev_fd >= 0)) {
            ret = bb_net_dev_create(net_dev_fd, ar_netif);
            if (!ret) {
                printf("Create net dev success!\n");

                memset(sys_cmd, 0, sizeof(sys_cmd));
                if (b_ip) {
                    sprintf(sys_cmd, "ifconfig %s %s", ar_netif->name, ip_str);
                    system(sys_cmd);
                }

                memset(sys_cmd, 0, sizeof(sys_cmd));
                if (b_mtu) {
                    sprintf(sys_cmd, "ifconfig %s mtu %d", ar_netif->name, mtu);
                    system(sys_cmd);
                }
            }
            else {
                printf("Create net dev failed! ret %d\n", ret);
            }
        }
        net_create_event = false;
        usleep(100*1000);
    }
}

int main(int argc, char *argv[])
{
    int ret = 0;
    int i;
    int opt = 0;
    int id = 0;
    char fd_name[256] = {0};
    char fd_drv_name[256] = {0};
    char mac_str[256] = {0};
    char sys_cmd[256] = {0};
    unsigned char mac_dft[AR_NET_MAC_LEN] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
    unsigned long mac_tmp[AR_NET_MAC_LEN] = {0};
    bool b_create = false;
    bool b_destroy = false;
    bool b_resize = false;
    bool b_ioctl_mode = false;
    bool b_get_version = false;
    bool b_get_drv_version = false;
    bool b_daemon = false;
    ar_netif_t ar_netif = {0};
    int fd = -1;
    int fd_drv = -1;
    int dev_num;
    bb_dev_t** devs=NULL;

    if(argc == 1){
        cmd_help(argv[0]);
        return -1;
    }

    ar_netif.slot = 0;
    ar_netif.socket_port = 0;
    ar_netif.tx_buf_size = 40000;
    ar_netif.rx_buf_size = 40000;
    mac_dft[0] = 0x00;
    for (i = 1;i<AR_NET_MAC_LEN;i++) {
        mac_dft[i] = RANDOM(16);
    }
    memcpy(ar_netif.mac, mac_dft, AR_NET_MAC_LEN);
    ar_netif.op_type = AR_NET_OP_CREATE;
    ar_netif.type = AR_NET_NETIF_T_PORT;

    while ((opt =
        getopt_long(argc, argv, "chvdn:m:s:r:t:i:p:T:IVP:M:DR", main_options, NULL)) != -1) {
        switch (opt) {
        case 'i':
            id = (int)strtoul(optarg, NULL, 10);
            break;
        case 'n':
            strcpy(ar_netif.name, optarg);
            printf("name %s\n", ar_netif.name);
            break;
        case 'm':
            strcpy(mac_str, optarg);
            sscanf(mac_str, "%02x:%02x:%02x:%02x:%02x:%02x",
                &mac_tmp[0], &mac_tmp[1],
                &mac_tmp[2], &mac_tmp[3],
                &mac_tmp[4], &mac_tmp[5]);
            for (i = 0; i < AR_NET_MAC_LEN;i++) {
                ar_netif.mac[i] = mac_tmp[i];
            }
            break;
        case 'r':
            ar_netif.rx_buf_size = (unsigned long)strtoul(optarg, NULL, 10);
            break;
        case 't':
            ar_netif.tx_buf_size = (unsigned long)strtoul(optarg, NULL, 10);
            break;
        case 'T':
            ar_netif.type = (unsigned char)strtoul(optarg, NULL, 10);
            break;
        case 's':
            ar_netif.slot = (unsigned char)strtoul(optarg, NULL, 10);
            break;
        case 'p':
            ar_netif.socket_port = (unsigned short)strtoul(optarg, NULL, 10);
            break;
        case 'c':
            b_create = true;
            break;
        case 'd':
            b_destroy = true;
            break;
        case 'R':
            b_resize = true;
            break;
        case 'I':
            b_ioctl_mode = true;
            break;
        case 'v':
            b_get_version = true;
            break;
        case 'V':
            b_get_drv_version = true;
            break;
        case 'P':
            b_ip = true;
            strcpy(ip_str, optarg);
            printf("ip %s\n", ip_str);
            break;
        case 'M':
            b_mtu = true;
            mtu = (unsigned long)strtoul(optarg, NULL, 10);
            break;
        case 'D':
            b_daemon = true;
            break;
        case 'h':
        default:
            cmd_help(argv[0]);
            return 0;
        }
    }

    if (b_ioctl_mode) {
        if (id < 0) {
            printf("id %d Invalid!\n", id);
            return 0;
        }

        sprintf(fd_name, "/dev/ar_net%d", id);
        fd = open(fd_name, O_RDWR);
        if (fd == -1){
            printf("Open device failed! %s\n", strerror(errno));
            return 0;
        }

        sprintf(fd_drv_name, "/dev/ar_mdev%d", id);
        fd_drv = open(fd_drv_name, O_RDWR);
        if (fd == -1){
            printf("Open device failed! %s\n", strerror(errno));
            return 0;
        }

        if (b_get_version) {
            cmd_version_get(fd);
            close(fd);
            return 0;
        }

        if (b_get_drv_version) {
            cmd_version_drv_get(fd_drv);
            close(fd_drv);
            return 0;
        }
        if (b_resize) {
            printf("Resize %s slot:[%d] port:[%d] tx_buf_size:[%d] rx_buf_size:[%d] type[%s]\n",
                                            ar_netif.name,
                                            ar_netif.slot,
                                            ar_netif.socket_port,
                                            ar_netif.tx_buf_size,
                                            ar_netif.rx_buf_size,
                                            (ar_netif.type == AR_NET_NETIF_T_PORT)?"port":((ar_netif.type == AR_NET_NETIF_T_VLAN)?"vlan":"Unknow"));
            ar_netif.op_type = AR_NET_OP_BUF_RESIZE;
        }
        else if (b_create) {
            printf("Create %s slot:[%d] port:[%d] tx_buf_size:[%d] rx_buf_size:[%d] type[%s]\n",
                                            ar_netif.name,
                                            ar_netif.slot,
                                            ar_netif.socket_port,
                                            ar_netif.tx_buf_size,
                                            ar_netif.rx_buf_size,
                                            (ar_netif.type == AR_NET_NETIF_T_PORT)?"port":((ar_netif.type == AR_NET_NETIF_T_VLAN)?"vlan":"Unknow"));
            ar_netif.op_type = AR_NET_OP_CREATE;
        }
        else if (b_destroy) {
            printf("Destroy %s slot:[%d] port:[%d] tx_buf_size:[%d] rx_buf_size:[%d] type[%s]\n",
                                            ar_netif.name,
                                            ar_netif.slot,
                                            ar_netif.socket_port,
                                            ar_netif.tx_buf_size,
                                            ar_netif.rx_buf_size,
                                            (ar_netif.type == AR_NET_NETIF_T_PORT)?"port":((ar_netif.type == AR_NET_NETIF_T_VLAN)?"vlan":"Unknow"));
            ar_netif.op_type = AR_NET_OP_DESTORY;
        }

        ret = cmd_port_handle(fd, &ar_netif);
    }
    else {
        while (true) {
            /* Connect to the daemon */
            ret = bb_host_connect(&phost,"127.0.0.1",BB_PORT_DEFAULT);
            if (ret || !phost)
            {
                printf("bb connect error,ret=%d\n",ret);
                sleep(1);
                continue;
            }
            printf("bb connect success\n");

            /* get dev list of the 8030 */
            dev_num = bb_dev_getlist(phost,&devs);
            if (dev_num == 0) {
                printf("Get no device!\n");
                sleep(1);
                continue;
            }
            printf("dev num %d\n", dev_num);

            /* Open the dev 8030 */
            bb_handle = bb_dev_open(devs[0]);
            if (!bb_handle) {
                printf("bb_dev_open error\n");
                sleep(1);
                continue;
            }
            printf("bb_dev_open success\n");

            bb_dev_freelist(devs);

            net_dev_fd = bb_net_dev_open(bb_handle);
            if (net_dev_fd < 0) {
                printf("bb_net_dev_open error\n");
                bb_dev_close(bb_handle);
                sleep(1);
                continue;
            }
            printf("net dev fd open success\n");

            break;
        }

        if (b_resize) {
            printf("Resize %s slot:[%d] port:[%d] tx_buf_size:[%d] rx_buf_size:[%d] type[%s]\n",
                                            ar_netif.name,
                                            ar_netif.slot,
                                            ar_netif.socket_port,
                                            ar_netif.tx_buf_size,
                                            ar_netif.rx_buf_size,
                                            (ar_netif.type == AR_NET_NETIF_T_PORT)?"port":((ar_netif.type == AR_NET_NETIF_T_VLAN)?"vlan":"Unknow"));
            ret = bb_net_dev_buf_resize(net_dev_fd, &ar_netif);
        }
        else if (b_create) {
            printf("Create %s slot:[%d] port:[%d] tx_buf_size:[%d] rx_buf_size:[%d] type[%s]\n",
                                            ar_netif.name,
                                            ar_netif.slot,
                                            ar_netif.socket_port,
                                            ar_netif.tx_buf_size,
                                            ar_netif.rx_buf_size,
                                            (ar_netif.type == AR_NET_NETIF_T_PORT)?"port":((ar_netif.type == AR_NET_NETIF_T_VLAN)?"vlan":"Unknow"));
            ret = bb_net_dev_create(net_dev_fd, &ar_netif);
        }
        else if (b_destroy) {
            printf("Destroy %s slot:[%d] port:[%d] tx_buf_size:[%d] rx_buf_size:[%d] type[%s]\n",
                                            ar_netif.name,
                                            ar_netif.slot,
                                            ar_netif.socket_port,
                                            ar_netif.tx_buf_size,
                                            ar_netif.rx_buf_size,
                                            (ar_netif.type == AR_NET_NETIF_T_PORT)?"port":((ar_netif.type == AR_NET_NETIF_T_VLAN)?"vlan":"Unknow"));
            ret = bb_net_dev_destroy(net_dev_fd, ar_netif.slot, ar_netif.socket_port);
        }

        bb_net_dev_close(net_dev_fd);
    }

    if (!ret) {
        printf("Handle port success!\n");

        memset(sys_cmd, 0, sizeof(sys_cmd));
        if (b_ip) {
            sprintf(sys_cmd, "ifconfig %s %s", ar_netif.name, ip_str);
            system(sys_cmd);
        }

        memset(sys_cmd, 0, sizeof(sys_cmd));
        if (b_mtu) {
            sprintf(sys_cmd, "ifconfig %s mtu %d", ar_netif.name, mtu);
            system(sys_cmd);
        }
    }
    else {
        printf("Handle port failed! ret %d\n", ret);
    }

    if (!b_ioctl_mode && b_daemon) {
        bb_dev_reg_hotplug_cb(phost, bb_event_hotplug, NULL);

        ret = MID_UTIL_CreateDetachThread(net_create_event_process, (void*)&ar_netif, NULL);
        if(ret < 0)
        {
            printf("create net_create_event_process task fail!\n");
            goto end;
        }

        while(1) {
            usleep(1000*1000);
        }
    }

end:
    close(fd);

    return 0;
}